/* -*-C++-*- */


/** \addtogroup irdefs
  * @{
  */

/**
  P4 compiler IR definition file.
  To understand this you really have to understand the shape of the generated IR code.

  The IR builder "knows" the following methods for a class T:

  cstring toString() const override;
  void dbprint(std::ostream &out) const override;
  bool operator==(const T &a) const;
  void validate() const;
  const char *node_type_name() const;
  void visit_children(Visitor &v);
  void dump_fields(std::ostream& out) const;

  C comments are ignored.
  C++ line comments can appear in some places and are emitted in the output.

  #emit/#end      -> copy text literally to output header file
  #emit_impl/#end -> copy text literally to output C++ file
  #noXXX          -> do not emit the specified implementation for the XXX method
                     e.g., #noconstructor, #nodbprint, #novisit_children
  #apply          -> generate apply overload for visitors
  method{ ... }   -> specifies an implementation for a default method
                     method can be 'operator=='

  Some assignments are turned into methods returning constant values:
  stringOp     -> cstring getStringOp() const
  precedence   -> int getPrecedence() const

  When defining fields and methods in an IR class, all fields and arguments of any IR
  class type are automatically converted into const IR::class * fields or arguments, unless
  they are identified as 'inline'.  It is not possible to create a non-const pointer to
  an IR class in any other IR class.

  There are some special keywords that can be applied (as decl modifiers) to fields that
  affect how they are created, checked, and initialized.

  inline      The field (of IR class type) should be defined directly in the object
              rather than being converted to a const T * as described above.
  NullOK      The field is a ppointer and may be null (verify will check that it is not otherwise)
  optional    The field may be proveded as an argument to the constructor but need not be.
              (overloaded constructors will be created as needed)

  Unless there is a '#noconstructor' tag in the class, a constructor
  will automatically be generated that takes as arguments values to
  initialize all fields of the IR class and its bases that do not have
  explicit initializers.  Fields marked 'optional' will create multiple
  constructors both with and without an argument for that field.
 */

class ParserState : ISimpleNamespace, Declaration, IAnnotated {
    optional Annotations                        annotations = Annotations::empty;
    optional inline IndexedVector<StatOrDecl>   components;
    // selectExpression can be a SelectExpression, or a PathExpression representing a state
    NullOK Expression                   selectExpression;

    Annotations getAnnotations() const override { return annotations; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return components.getDeclarations(); }
    const IDeclaration* getDeclByName(cstring name) const override {
        return components.getDeclaration(name); }

    static const cstring accept;
    static const cstring reject;
    static const cstring start;
    static const cstring verify;
    bool isBuiltin() const { return name == ParserState::accept || name == ParserState::reject; }
    validate{
        if (selectExpression != nullptr)
            BUG_CHECK(selectExpression->is<IR::PathExpression>() ||
                      selectExpression->is<IR::SelectExpression>(),
                      "%1%: unexpected select expression", selectExpression);
    }
}

// A parser that contains all states (unlike the P4 v1.0 parser, which is really just a state)
class P4Parser : Type_Declaration, INestedNamespace, ISimpleNamespace, IApply, IContainer, IAnnotated {
    Type_Parser                                 type;
    optional ParameterList                      constructorParams = new ParameterList;
    optional inline IndexedVector<Declaration>  parserLocals;
    optional inline IndexedVector<ParserState>  states;

    Annotations getAnnotations() const override { return type->getAnnotations(); }
    TypeParameters getTypeParameters() const override { return type->getTypeParameters(); }
    std::vector<INamespace> getNestedNamespaces() const override {
        return { type->typeParameters, type->applyParams, constructorParams }; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return parserLocals.getDeclarations()->concat(states.getDeclarations()); }
    IDeclaration getDeclByName(cstring name) const override {
        auto decl = parserLocals.getDeclaration(name);
        if (!decl) decl = states.getDeclaration(name);
        return decl; }
    Type_Method getApplyMethodType() const override { return type->getApplyMethodType(); }
    ParameterList getApplyParameters() const override { return type->getApplyParameters(); }
    Type_Method getConstructorMethodType() const override;
    ParameterList getConstructorParameters() const override { return constructorParams; }
    void checkDuplicates() const;
    Type getType() const override { return this->type; }
#apply
    validate {
        if (!(name == type->name))
            BUG("Name mismatch for %1%: %2% != %3%", this, name, type->name);
        parserLocals.check_null();
        states.check_null();
        checkDuplicates();
        for (auto d : parserLocals)
            BUG_CHECK(!d->is<ParserState>(), "%1%: state in locals", d);
    }
    toString { return cstring("parser ") + externalName(); }
}

class P4Control : Type_Declaration, INestedNamespace, ISimpleNamespace, IApply, IContainer, IAnnotated {
    Type_Control                                type;
    optional ParameterList                      constructorParams = new ParameterList;
    optional inline IndexedVector<Declaration>  controlLocals;
    BlockStatement                              body;

    Annotations getAnnotations() const override { return type->getAnnotations(); }
    TypeParameters getTypeParameters() const override { return type->getTypeParameters(); }
    std::vector<INamespace> getNestedNamespaces() const override {
        return { type->typeParameters, type->applyParams, constructorParams }; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return controlLocals.getDeclarations(); }
    Type_Method getApplyMethodType() const override { return type->getApplyMethodType(); }
    ParameterList getApplyParameters() const override { return type->getApplyParameters(); }
    Type_Method getConstructorMethodType() const override;
    IDeclaration getDeclByName(cstring name) const override {
        return controlLocals.getDeclaration(name); }
    ParameterList getConstructorParameters() const override { return constructorParams; }
    Type getType() const override { return this->type; }
#apply
    validate {
        if (!(name == type->name))
            BUG("Name mismatch for %1%: %2% != %3%", this, name, type->name);
        controlLocals.check_null();
    }
    toString { return cstring("control ") + externalName(); }
}

/// A P4-16 action
class P4Action : Declaration, ISimpleNamespace, IAnnotated, IFunctional {
    optional Annotations        annotations = Annotations::empty;
    ParameterList               parameters;
    BlockStatement              body;
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return parameters->getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return parameters->getDeclByName(name); }
    Annotations getAnnotations() const override { return annotations; }
    ParameterList getParameters() const override { return parameters; }
}

class Type_Error : ISimpleNamespace, Type_Declaration {
    static const cstring error;
    optional inline IndexedVector<Declaration_ID> members;
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return members.getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return members.getDeclaration(name); }
    validate{ members.check_null(); }
}

// Not a subclass of IDeclaration
class Declaration_MatchKind : ISimpleNamespace {
    optional inline IndexedVector<Declaration_ID> members;
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return members.getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return members.getDeclaration(name); }
    validate{ members.check_null(); }
}

/// Table property value abstract base class
abstract PropertyValue { }

/// A table property whose value is an expression
class ExpressionValue : PropertyValue {
    Expression expression;
    dbprint { out << expression; }
}

class ExpressionListValue : PropertyValue {
    inline Vector<Expression> expressions;
    dbprint { out << expressions; }
}

// An element in a table actions list
class ActionListElement : IAnnotated, IDeclaration {
    optional Annotations annotations = Annotations::empty;
    Expression           expression;  // This can be a PathExpression or a MethodCallExpression
    dbprint { out << annotations << expression; }
    ID getName() const override { return getPath()->name; }
    Path getPath() const;
    Annotations getAnnotations() const override { return annotations; }
    validate {
        BUG_CHECK(expression->is<IR::PathExpression>() ||
                  expression->is<IR::MethodCallExpression>(),
                  "%1%: unexpected expression", expression);
    }
    toString{ return getName().toString(); }
}

// A list of actions (in a table)
class ActionList : PropertyValue {
    inline IndexedVector<ActionListElement> actionList;
    validate{ actionList.check_null(); }
    size_t size() const { return actionList.size(); }
    void push_back(ActionListElement e) { actionList.push_back(e); }
    ActionListElement getDeclaration(cstring n) const {
        return actionList.getDeclaration<ActionListElement>(n); }
}

class KeyElement : IAnnotated {
    optional Annotations annotations = Annotations::empty;
    Expression           expression;
    PathExpression       matchType;

    Annotations     getAnnotations() const override { return annotations; }
    const IR::Node *transform_visit(Transform &v) {
        // call this from Transform::preorder(KeyElement) if the transform might split
        // the expression into a Vector<Expression>
        v.visit(annotations, "annotations");
        auto exp = v.apply_visitor(expression, "expression");
        v.visit(matchType, "matchType");
        v.prune();
        if (exp == expression) {
        } else if (auto vec = exp->to<Vector<Expression>>()) {
            auto *rv = new Vector<KeyElement>();
            for (auto el : *vec) {
                auto *kel = clone();
                kel->expression = el;
                rv->push_back(kel); }
            return rv;
        } else {
            expression = exp->to<IR::Expression>(); }
        return this; }
}

// Value of a table key property
class Key : PropertyValue {
    inline Vector<KeyElement> keyElements;
    validate { keyElements.check_null(); }
    void push_back(KeyElement ke) { keyElements.push_back(ke); }
}

/// Pre-defined entry in a table
class Entry : IAnnotated {
    /// annotations are optional (supported annotations: @priority(value))
    optional Annotations annotations = Annotations::empty;
    ListExpression      keys;         /// must be a tuple expression
    Expression          action;       /// typically a MethodCallExpression.
                                      /// The action must be defined in action list
    bool                singleton;    /// True if the entry is not a list.

    Annotations     getAnnotations() const override { return annotations; }
    ListExpression  getKeys() const { return keys; }
    Expression      getAction() const { return action; }
    dbprint { out << annotations << keys << action; }
}

/// List of predefined entries. Part of table properties
class EntriesList : PropertyValue {
    inline Vector<Entry> entries;
    size_t size() const { return entries.size(); }
    dbprint { out << "{ " << entries << "}"; }
}

class Property : Declaration, IAnnotated {
    optional Annotations   annotations = Annotations::empty;
    PropertyValue value;
    bool          isConstant;
    Annotations getAnnotations() const override { return annotations; }
    dbprint { out << annotations << (isConstant ? "const " : "") << name << " = " << value; }
}

class TableProperties : ISimpleNamespace {
    optional inline IndexedVector<Property> properties;
    toString{ return "TableProperties(" + Util::toString(properties.size()) + ")"; }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return properties.getDeclarations(); }
    Property getProperty(cstring name) const {
        return properties.getDeclaration<Property>(name); }
    IDeclaration getDeclByName(cstring name) const override {
        return properties.getDeclaration(name); }
    void push_back(Property prop) { properties.push_back(prop); }

    static const cstring actionsPropertyName;
    static const cstring keyPropertyName;
    static const cstring defaultActionPropertyName;
    static const cstring entriesPropertyName;
    static const cstring sizePropertyName;
#nodbprint
    validate{ properties.check_null(); properties.validate(); }
}

class P4Table : Declaration, IAnnotated, IApply {
    optional Annotations        annotations = Annotations::empty;
    TableProperties             properties;

    Annotations getAnnotations() const override { return annotations; }
    Type_Method getApplyMethodType() const override;
    ParameterList getApplyParameters() const override { return new ParameterList(); }
    ActionList getActionList() const {
        auto ap = properties->getProperty(TableProperties::actionsPropertyName);
        if (ap == nullptr)
            return nullptr;
        if (!ap->value->is<IR::ActionList>()) {
            ::error(ErrorType::ERR_INVALID, "%1%: must be an action list", ap);
            return nullptr; }
        return ap->value->to<IR::ActionList>(); }
    Key getKey() const {
        auto kp = properties->getProperty(TableProperties::keyPropertyName);
        if (kp == nullptr)
            return nullptr;
        if (!kp->value->is<IR::Key>()) {
            ::error(ErrorType::ERR_INVALID, "%1%: must be a key", kp);
            return nullptr; }
        return kp->value->to<IR::Key>(); }
    Expression getDefaultAction() const {
        auto d = properties->getProperty(TableProperties::defaultActionPropertyName);
        if (d == nullptr)
            return nullptr;
        if (!d->value->is<IR::ExpressionValue>()) {
            ::error(ErrorType::ERR_INVALID, "%1%: must be an expression", d);
            return nullptr; }
        return d->value->to<IR::ExpressionValue>()->expression; }
    Constant getConstantProperty(cstring name) const {
        if (auto d = properties->getProperty(name)) {
            if (auto ev = d->value->to<IR::ExpressionValue>()) {
                if (auto k = ev->expression->to<IR::Constant>()) {
                    return k; } }
            error(ErrorType::ERR_INVALID, "%1% must be a constant numeric expression", d); }
        return nullptr; }
    Constant getSizeProperty() const {
        return getConstantProperty(TableProperties::sizePropertyName);
    }
    EntriesList getEntries() const {
        auto ep = properties->getProperty(TableProperties::entriesPropertyName);
        if (ep == nullptr)
            return nullptr;
        if (!ep->value->is<IR::EntriesList>()) {
            ::error(ErrorType::ERR_INVALID, "%1%: must be a list of entries", ep);
            return nullptr;
        }
        return ep->value->to<IR::EntriesList>();
    }
}

class P4ValueSet : Declaration, IAnnotated {
    optional Annotations        annotations = Annotations::empty;
    Type                        elementType;
    Expression                  size; // number of elements in set.
    Annotations getAnnotations() const override { return annotations; }
}

class Declaration_Variable : Declaration, IAnnotated {
    optional Annotations        annotations = Annotations::empty;
    Type                        type;
    optional NullOK Expression  initializer = nullptr;

    Annotations getAnnotations() const override { return annotations; }
    dbprint {
        out << annotations << type << ' ' << name;
        if (initializer) out << " = " << *initializer; }
}

class Declaration_Constant : Declaration, IAnnotated {
    optional Annotations        annotations = Annotations::empty;
    Type                        type;
    Expression                  initializer;

    Annotations getAnnotations() const override { return annotations; }
    toString { return Declaration::toString(); }
    dbprint { out << annotations << type << ' ' << name << " = " << *initializer; }
}

/// Like a variable, but for a statically allocated instance.
/// The syntax is Contructor(args) name = initializer;
/// Initializers are an experimental features, used for externs with
/// abstract methods.
class Declaration_Instance : Declaration, IAnnotated, IInstance {
    optional Annotations  annotations = Annotations::empty;
    Type                  type;  // Either Type_Name or Type_Specialized or Type_Extern
    Vector<Argument>      arguments;
    inline NameMap<Property> properties = {};  // P4_14 externs only, at the moment
    optional NullOK BlockStatement initializer = nullptr;
        // experimental only; contains just declarations, no code

    Annotations getAnnotations() const override { return annotations; }
    Type getType() const override { return type; }
    cstring Name() const override { return name; }
    validate{ BUG_CHECK(type->is<Type_Name>() ||
                        type->is<Type_Specialized>() ||
                        type->is<Type_Extern>(),        // P4_14 only?
                        "%1%: unexpected type", type);
        arguments->check_null(); }
}

/// Toplevel program representation
class P4Program : IGeneralNamespace {
    /// Top-level program objects.
    /// This is not an IndexedVector because:
    /// - we allow overloaded function-like objects.
    /// - not all objects in a P4Program are declarations (e.g., match_kind is not).
    optional inline Vector<Node> objects;
    Util::Enumerator<IDeclaration>* getDeclarations() const override;
    validate{ objects.check_null(); }
    static const cstring main;
#apply
}

///////////////////////////// Statements //////////////////////////

abstract Statement : StatOrDecl {}

class ExitStatement : Statement {
    toString{ return "exit"; }
    dbprint { out << "exit"; }
}

class ReturnStatement : Statement {
    NullOK Expression expression;
    toString{ return cstring("return ") +
                (expression != nullptr ? expression->toString() : cstring("")); }
}

class EmptyStatement : Statement {
    dbprint { out << ""; }
}

class AssignmentStatement : Statement {
    Expression left;
    Expression right;
}

class IfStatement : Statement {
    Expression       condition;
    Statement        ifTrue;
    NullOK Statement ifFalse;
    visit_children {
        v.visit(condition, "condition");
        SplitFlowVisit<Statement>(v, ifTrue, ifFalse).run_visit(); }
}

class BlockStatement : Statement, ISimpleNamespace, IAnnotated {
    optional Annotations                        annotations = Annotations::empty;
    optional inline IndexedVector<StatOrDecl>   components;
    IDeclaration getDeclByName(cstring name) const override {
        return components.getDeclaration(name); }
    Util::Enumerator<IDeclaration>* getDeclarations() const override {
        return components.getDeclarations(); }
    void push_back(StatOrDecl st) { components.push_back(st); }
    Annotations getAnnotations() const override { return annotations; }
    bool empty() const { return components.empty(); }
}

class MethodCallStatement : Statement {
    MethodCallExpression methodCall;
    MethodCallStatement { if (!srcInfo) srcInfo = methodCall->srcInfo; }
    MethodCallStatement(Util::SourceInfo si, IR::ID m, const std::initializer_list<Argument> &a)
    : Statement(si), methodCall(new MethodCallExpression(si, m, a)) {}
    MethodCallStatement(Util::SourceInfo si, Expression m,
                        const std::initializer_list<Argument> &a)
    : Statement(si), methodCall(new MethodCallExpression(si, m, a)) {}
    toString{ return methodCall->toString(); }
}

class SwitchCase {
    Expression       label;
    NullOK Statement statement;  // If missing then it's a fall-through
#nodbprint
    validate{
        BUG_CHECK(statement == nullptr || statement->is<IR::BlockStatement>(),
                  "%1%: Expected a block statement",
                  statement);
    }
}

// The type system will enforce the fact that
// expression is IR::Member(IR::MethodCallExpression(table_apply), Type_Table::action_run)
class SwitchStatement : Statement {
    Expression expression;
    inline Vector<SwitchCase> cases;
    visit_children {
        v.visit(expression, "expression");
        SplitFlowVisit<SwitchCase> split(v);
        for (auto &c : cases) split.addNode(c);
        split.run_visit(); }
}

class Function : Declaration, IFunctional, ISimpleNamespace, INestedNamespace {
    Type_Method    type;
    BlockStatement body;
    ParameterList getParameters() const override {
        return type->parameters;
    }
    Util::Enumerator<IDeclaration> *getDeclarations() const override {
        return type->parameters->getDeclarations(); }
    IDeclaration getDeclByName(cstring name) const override {
        return type->parameters->getDeclByName(name); }
    std::vector<INamespace> getNestedNamespaces() const override {
        return { type->typeParameters }; }
}

/////////////////////////////////////////////////////////////

/**
 * Block is the base class for IR nodes produced by the evaluator.
 * A block represents a compile-time allocated resource.
 * Blocks are not visited using visitors, so the visit_children()
 * method is empty.  Users have to write custom visitors to
 * traverse the constantValue map.
 */
abstract Block : CompileTimeValue {
    Node node;  /// Node that evaluates to this block.
    /// This is either a Declaration_Instance or a ConstructorCallExpression.

    /// One value for each Node inside that evaluates to a compile-time constant.
    /// This includes all constructor parameters, and all inner nested blocks.
    ordered_map<Node, CompileTimeValue> constantValue = {};

    virtual void dbprint(std::ostream& out) const override;
    virtual void dbprint_recursive(std::ostream& out) const;
    /// value can be null for parameters which are optional
    void setValue(Node node, CompileTimeValue value);
    bool hasValue(Node node) const {
        return constantValue.find(node) != constantValue.end();
    }
    CompileTimeValue getValue(Node node) const {
        CHECK_NULL(node);
        auto it = constantValue.find(node);
        BUG_CHECK(it != constantValue.end(), "%1%: No such node %2%", this, node);
        return it->second; }
    visit_children { (void)v; }
    virtual const IDeclaration* getContainer() const { return nullptr; }
}

class TableBlock : Block {
    P4Table container;
    const IDeclaration* getContainer() const override { return container; }
#nodbprint
}

/// An object that has been instantiated
abstract InstantiatedBlock : Block, IDeclaration {
    Type instanceType;  // May be a SpecializedType

    virtual ParameterList getConstructorParameters() const = 0;
    void instantiate(std::vector<CompileTimeValue> *args);

    /// @return the argument that the given parameter was instantiated with.
    /// It's a fatal error if no such parameter exists.
    CompileTimeValue getParameterValue(cstring paramName) const;

    /// @return the argument that the given parameter was instantiated with, or
    /// null if no such parameter exists.
    CompileTimeValue findParameterValue(cstring paramName) const;

    virtual void dbprint(std::ostream& out) const override;
}

class ParserBlock : InstantiatedBlock {
    P4Parser container;
    ParameterList getConstructorParameters() const override {
        return container->constructorParams; }
    toString { return container->toString(); }
    ID getName() const override { return container->getName(); }
    const IDeclaration* getContainer() const override { return container; }
#nodbprint
}

class ControlBlock : InstantiatedBlock {
    P4Control container;
    ParameterList getConstructorParameters() const override {
        return container->constructorParams; }
    toString { return container->toString(); }
    ID getName() const override { return container->getName(); }
    const IDeclaration* getContainer() const override { return container; }
#nodbprint
}

class PackageBlock : InstantiatedBlock {
    Type_Package type;
    ParameterList getConstructorParameters() const override { return type->constructorParams; }
    toString { return type->toString(); }
    ID getName() const override { return type->getName(); }
#nodbprint
}

class ExternBlock : InstantiatedBlock {
    Type_Extern type;
    Method      constructor;  // used to instantiate this block
    ParameterList getConstructorParameters() const override {
        return constructor->type->parameters; }
    toString { return type->toString(); }
    ID getName() const override { return type->getName(); }
#nodbprint
}

/// Represents the program as a whole
class ToplevelBlock : Block, IDeclaration {
    P4Program getProgram() const { return node->to<IR::P4Program>(); }
    PackageBlock getMain() const;
    ID getName() const override { return "main"; }
#nodbprint
    validate { BUG_CHECK(node->is<IR::P4Program>(), "%1%: expected a P4Program", node); }
}

/** @} *//* end group irdefs */
